﻿package zdc {
import flash.display.*;

/**
 * based on source code found at:
 * http://www.macromedia.com/devnet/mx/flash/articles/adv_draw_methods.html
 *
 * @author Ric Ewing - version 1.4 - 4.7.2002
 * @author Kevin Williams - version 2.0 - 4.7.2005
 * @author Aden Forshaw - Version AS3 - 19.4.2010
 * @author Sidney de Koning - Version AS3 - 20.4.2010 - errors/correct datatypes/optimized math operations
 *         Arie de Bonth - Version AS3 - 09.04.2011 - update/rewrite of drawLine() function
 *
 * Usage:
 * var s : Shape = new Shape( ); // Or Sprite of MovieClip or any other Class that makes use of the Graphics class
 *
 * // Draw an ARC
 * s.graphics.lineStyle( 4, 0xE16606 );
 * DrawingShapes.drawArc( s.graphics, 50, 50, 10, 150, 60 );
 *
 * // Draw an BURST
 * s.graphics.lineStyle( 3, 0x000000 );
 * DrawingShapes.drawBurst( s.graphics, 80, 60, 3, 15, 6, 27 );
 *
 * // Draw an DASHED-LINE like so - - - -
 * s.graphics.lineStyle( 1, 0x3C3C39 );
 * DrawingShapes.drawDash( s.graphics, 120, 60, 150, 80, 2, 2 );
 *
 * // Draw an GEAR
 * s.graphics.lineStyle( 3, 0xE16606 );
 * DrawingShapes.drawGear( s.graphics, 200, 60, 13, 31, 26, 0, 7, 13 );
 *
 * // Draw a POLYGON
 * s.graphics.lineStyle( 3, 0x0074B9 );
 * DrawingShapes.drawPolygon( s.graphics, 270, 60, 7, 30, 45 );
 *
 * // Draw a STAR
 * s.graphics.lineStyle( 2, 0x000000 );
 * DrawingShapes.drawStar( s.graphics, 340, 60, 18, 24, 19, 27 );
 *
 * // Draw an WEDGE - good for pie charts or pacmans
 * s.graphics.lineStyle( 2, 0xFFCC00 );
 * DrawingShapes.drawWedge( s.graphics, 400, 60, 30, 309, 209 );
 *
 * // Draw a LINE
 * s.graphics.lineStyle( 2, 0x0074B9 );
 * DrawingShapes.drawLine( s.graphics, 440, 85, 30, DrawingShapes.VERTICAL_LINE );
 *
 * addChild( s );
 */
public class DrawingShapes {

    public static const HORIZONTAL_LINE:String = "DrawingShapes.horizontal";
    public static const VERTICAL_LINE:String = "DrawingShapes.vertical";


    public function DrawingShapes() {
        throw new ArgumentError("The DrawingShapes Class cannot be instanicated.");
    }

    /**
     * drawDash
     * Draws a dashed line from the point x1,y1 to the point x2,y2
     *
     * @param target Graphics the Graphics Class on which the dashed line will be drawn.
     * @param x1 Number starting position on x axis - <strong></strong>required</strong>
     * @param y1 Number starting position on y axis - <strong></strong>required</strong>
     * @param x2 Number finishing position on x axis - <strong></strong>required</strong>
     * @param y2 Number finishing position on y axis - <strong></strong>required</strong>
     * @param dashLength [optional] Number the number of pixels long each dash
     * will be.  Default = 5
     * @param spaceLength [optional] Number the number of pixels between each
     * dash.  Default = 5
     */
    public static function drawDash(target:Graphics, x1:Number, y1:Number, x2:Number, y2:Number, dashLength:Number = 5, spaceLength:Number = 5):void {

        var x:Number = x2 - x1;
        var y:Number = y2 - y1;
        var hyp:Number = Math.sqrt((x) * (x) + (y) * (y));
        var units:Number = hyp / (dashLength + spaceLength);
        var dashSpaceRatio:Number = dashLength / (dashLength + spaceLength);
        var dashX:Number = (x / units) * dashSpaceRatio;
        var spaceX:Number = (x / units) - dashX;
        var dashY:Number = (y / units) * dashSpaceRatio;
        var spaceY:Number = (y / units) - dashY;

        target.moveTo(x1, y1);
        while (hyp > 0) {
            x1 += dashX;
            y1 += dashY;
            hyp -= dashLength;
            if (hyp < 0) {
                x1 = x2;
                y1 = y2;
            }
            target.lineTo(x1, y1);
            x1 += spaceX;
            y1 += spaceY;
            target.moveTo(x1, y1);
            hyp -= spaceLength;
        }
        target.moveTo(x2, y2);
    }

    /**
     * Draws an arc from the starting position of x,y.
     *
     * @param target the Graphics Class that the Arc is drawn on.
     * @param x x coordinate of the starting pen position
     * @param y y coordinate of the starting pen position
     * @param radius radius of Arc.
     * @param arc = sweep of the arc. Negative values draw clockwise.
     * @param startAngle = [optional] starting offset angle in degrees.
     * @param yRadius = [optional] y radius of arc. if different than
     * radius, then the arc will draw as the arc of an oval.
     * default = radius.
     *
     * Based on mc.drawArc by Ric Ewing.
     * the version by Ric assumes that the pen is at x:y before this
     * method is called.  I explictily move the pen to x:y to be
     * consistent with the behaviour of the other methods.
     */
    public static function drawArc(target:Graphics, x:Number, y:Number, radius:Number, arc:Number, startAngle:Number = 0, yRadius:Number = 0):void {

        if (arguments.length < 5) {
            throw new ArgumentError("DrawingShapes.drawArc() - too few parameters, need atleast 5.");
            return;
        }

        // if startAngle is undefined, startAngle = 0
        if (startAngle == 0) {
            startAngle = 0;
        }
        // if yRadius is undefined, yRadius = radius
        if (yRadius == 0) {
            yRadius = radius;
        }

        // Init vars
        var segAngle:Number, theta:Number, angle:Number, angleMid:Number, segs:Number, ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number;
        // no sense in drawing more than is needed :)
        if (DrawingShapes.abs(arc) > 360) {
            arc = 360;
        }
        // Flash uses 8 segments per circle, to match that, we draw in a maximum
        // of 45 degree segments. First we calculate how many segments are needed
        // for our arc.
        segs = DrawingShapes.ceil(DrawingShapes.abs(arc) / 45);
        // Now calculate the sweep of each segment
        segAngle = arc / segs;
        // The math requires radians rather than degrees. To convert from degrees
        // use the formula (degrees/180)*Math.PI to get radians.
        theta = -(segAngle / 180) * Math.PI;
        // convert angle startAngle to radians
        angle = -(startAngle / 180) * Math.PI;
        // find our starting points (ax,ay) relative to the secified x,y
        ax = x - Math.cos(angle) * radius;
        ay = y - Math.sin(angle) * yRadius;
        // if our arc is larger than 45 degrees, draw as 45 degree segments
        // so that we match Flash's native circle routines.
        if (segs > 0) {
            target.moveTo(x, y);
            // Loop for drawing arc segments
            for (var i:int = 0; i < segs; ++i) {
                // increment our angle
                angle += theta;
                // find the angle halfway between the last angle and the new
                angleMid = angle - (theta / 2);
                // calculate our end point
                bx = ax + Math.cos(angle) * radius;
                by = ay + Math.sin(angle) * yRadius;
                // calculate our control point
                cx = ax + Math.cos(angleMid) * (radius / Math.cos(theta / 2));
                cy = ay + Math.sin(angleMid) * (yRadius / Math.cos(theta / 2));
                // draw the arc segment
                target.curveTo(cx, cy, bx, by);
            }
        }
    }

    /**
     * draws pie shaped wedges.  Could be employeed to draw pie charts.
     *
     * @param target the Graphics on which the wedge is to be drawn.
     * @param x x coordinate of the center point of the wedge
     * @param y y coordinate of the center point of the wedge
     * @param radius the radius of the wedge
     * @param arc the sweep of the wedge. negative values draw clockwise
     * @param startAngle the starting angle in degrees
     * @param yRadius [optional] the y axis radius of the wedge.
     * If not defined, then yRadius = radius.
     *
     * based on mc.drawWedge() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawWedge(target:Graphics, x:Number, y:Number, radius:Number, arc:Number, startAngle:Number = 0, yRadius:Number = 0):void {

        // if yRadius is undefined, yRadius = radius
        if (yRadius == 0) {
            yRadius = radius;
        }

        // move to x,y position
        target.moveTo(x, y);
        // if yRadius is undefined, yRadius = radius
        if (yRadius == 0) {
            yRadius = radius;
        }
        // Init vars
        var segAngle:Number, theta:Number, angle:Number, angleMid:Number, segs:Number, ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number;
        // limit sweep to reasonable numbers
        if (DrawingShapes.abs(arc) > 360) {
            arc = 360;
        }
        // Flash uses 8 segments per circle, to match that, we draw in a maximum
        // of 45 degree segments. First we calculate how many segments are needed
        // for our arc.
        segs = DrawingShapes.ceil(DrawingShapes.abs(arc) / 45);
        // Now calculate the sweep of each segment.
        segAngle = arc / segs;
        // The math requires radians rather than degrees. To convert from degrees
        // use the formula (degrees/180)*Math.PI to get radians.
        theta = -(segAngle / 180) * Math.PI;
        // convert angle startAngle to radians
        angle = -(startAngle / 180) * Math.PI;
        // draw the curve in segments no larger than 45 degrees.
        if (segs > 0) {
            // draw a line from the center to the start of the curve
            ax = x + Math.cos(startAngle / 180 * Math.PI) * radius;
            ay = y + Math.sin(-startAngle / 180 * Math.PI) * yRadius;
            target.lineTo(ax, ay);
            // Loop for drawing curve segments
            for (var i:int = 0; i < segs; ++i) {
                angle += theta;
                angleMid = angle - (theta / 2);
                bx = x + Math.cos(angle) * radius;
                by = y + Math.sin(angle) * yRadius;
                cx = x + Math.cos(angleMid) * (radius / Math.cos(theta / 2));
                cy = y + Math.sin(angleMid) * (yRadius / Math.cos(theta / 2));
                target.curveTo(cx, cy, bx, by);
            }
            // close the wedge by drawing a line to the center
            target.lineTo(x, y);
        }
    }

    /**
     * start draws a star shaped polygon.
     *
     * <blockquote>Note that the stars by default 'point' to
     * the right. This is because the method starts drawing
     * at 0 degrees by default, putting the first point to
     * the right of center. Negative values for points
     * draws the star in reverse direction, allowing for
     * knock-outs when used as part of a mask.</blockquote>
     *
     * @param target the Graphics that the star is drawn on
     * @param x x coordinate of the center of the star
     * @param y y coordinate of the center of the star
     * @param points the number of points on the star
     * @param innerRadius the radius of the inside angles of the star
     * @param outerRadius the radius of the outside angles of the star
     * @param angle [optional] the offet angle that the start is rotated
     *
     * based on mc.drawStar() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawStar(target:Graphics, x:Number, y:Number, points:uint, innerRadius:Number, outerRadius:Number, angle:Number = 0):void {

        // check that points is sufficient to build polygon
        if (points <= 2) {
            throw ArgumentError("DrawingShapes.drawStar() - parameter 'points' needs to be atleast 3");
            return;
        }
        if (points > 2) {
            // init vars
            var step:Number, halfStep:Number, start:Number, n:Number, dx:Number, dy:Number;
            // calculate distance between points
            step = (Math.PI * 2) / points;
            halfStep = step / 2;
            // calculate starting angle in radians
            start = (angle / 180) * Math.PI;
            target.moveTo(x + (Math.cos(start) * outerRadius), y - (Math.sin(start) * outerRadius));
            // draw lines
            for (n = 1; n <= points; ++n) {
                dx = x + Math.cos(start + (step * n) - halfStep) * innerRadius;
                dy = y - Math.sin(start + (step * n) - halfStep) * innerRadius;
                target.lineTo(dx, dy);
                dx = x + Math.cos(start + (step * n)) * outerRadius;
                dy = y - Math.sin(start + (step * n)) * outerRadius;
                target.lineTo(dx, dy);
            }
        }
    }

    /**
     * a method for creating polygon shapes.  Negative values will draw
     * the polygon in reverse direction.  Negative drawing may be useful
     * for creating knock-outs in masks.
     *
     * @param target the Graphics that the polygon is to be drawn on
     * @param x x coordinate of the center of the polygon
     * @param y y coordinate of the center of the polygon
     * @param sides the number of sides (must be > 2)
     * @param radius the radius from the center point to the points
     * on the polygon
     * @param angle [optional] the starting offset angle (degrees) from
     * 0. Default = 0
     *
     * based on mc.drawPoly() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawPolygon(target:Graphics, x:Number, y:Number, sides:uint, radius:Number, angle:Number = 0):void {

        // check that sides is sufficient to build
        if (sides <= 2) {
            throw ArgumentError("DrawingShapes.drawPolygon() - parameter 'sides' needs to be atleast 3");
            return;
        }
        if (sides > 2) {
            // init vars
            var step:Number, start:Number, n:Number, dx:Number, dy:Number;
            // calculate span of sides
            step = (Math.PI * 2) / sides;
            // calculate starting angle in radians
            start = (angle / 180) * Math.PI;
            target.moveTo(x + (Math.cos(start) * radius), y - (Math.sin(start) * radius));
            // draw the polygon
            for (n = 1; n <= sides; ++n) {
                dx = x + Math.cos(start + (step * n)) * radius;
                dy = y - Math.sin(start + (step * n)) * radius;
                target.lineTo(dx, dy);
            }
        }
    }

    /**
     * Burst is a method for drawing star bursts.  If you've ever worked
     * with an advertising department, you know what they are ;-)
     * Clients tend to want them, Developers tend to hate them...
     *
     * @param target Graphics where the Burst is to be drawn.
     * @param x x coordinate of the center of the burst
     * @param y y coordinate of the center of the burst
     * @param sides number of sides or points
     * @param innerRadius radius of the indent of the curves
     * @param outerRadius radius of the outermost points
     * @param angle [optional] starting angle in degrees. (defaults to 0)
     *
     * based on mc.drawBurst() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawBurst(target:Graphics, x:Number, y:Number, sides:uint, innerRadius:Number, outerRadius:Number, angle:Number = 0):void {

        // check that sides is sufficient to build
        if (sides <= 2) {
            throw ArgumentError("DrawingShapes.drawBurst() - parameter 'sides' needs to be atleast 3");
            return;
        }
        if (sides > 2) {
            // init vars
            var step:Number, halfStep:Number, qtrStep:Number, start:Number, n:Number, dx:Number, dy:Number, cx:Number, cy:Number;
            // calculate length of sides
            step = (Math.PI * 2) / sides;
            halfStep = step / 2;
            qtrStep = step / 4;
            // calculate starting angle in radians
            start = (angle / 180) * Math.PI;
            target.moveTo(x + (Math.cos(start) * outerRadius), y - (Math.sin(start) * outerRadius));
            // draw curves
            for (n = 1; n <= sides; ++n) {
                cx = x + Math.cos(start + (step * n) - (qtrStep * 3)) * (innerRadius / Math.cos(qtrStep));
                cy = y - Math.sin(start + (step * n) - (qtrStep * 3)) * (innerRadius / Math.cos(qtrStep));
                dx = x + Math.cos(start + (step * n) - halfStep) * innerRadius;
                dy = y - Math.sin(start + (step * n) - halfStep) * innerRadius;
                target.curveTo(cx, cy, dx, dy);
                cx = x + Math.cos(start + (step * n) - qtrStep) * (innerRadius / Math.cos(qtrStep));
                cy = y - Math.sin(start + (step * n) - qtrStep) * (innerRadius / Math.cos(qtrStep));
                dx = x + Math.cos(start + (step * n)) * outerRadius;
                dy = y - Math.sin(start + (step * n)) * outerRadius;
                target.curveTo(cx, cy, dx, dy);
            }
        }
    }

    /**
     * draws a gear shape on the Graphics target.  The gear position
     * is indicated by the x and y arguments.
     *
     * @param target Graphics on which the gear is to be drawn.
     * @param x x coordinate of the center of the gear
     * @param y y coordinate of the center of the gear
     * @param sides number of teeth on gear. (must be > 2)
     * @param innerRadius radius of the indent of the teeth.
     * @param outerRadius outer radius of the teeth.
     * @param angle = [optional] starting angle in degrees. Defaults to 0.
     * @param holeSides [optional] draw a polygonal hole with this many sides (must be > 2)
     * @param holeRadius [optional] size of hole. Default = innerRadius/3.
     *
     * based on mc.drawGear() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawGear(target:Graphics, x:Number, y:Number, sides:uint, innerRadius:Number = 80, outerRadius:Number = 4, angle:Number = 0, holeSides:Number = 2, holeRadius:Number = 0):void {

        // check that sides is sufficient to build polygon
        if (sides <= 2) {
            throw ArgumentError("DrawingShapes.drawGear() - parameter 'sides' needs to be atleast 3");
            return;
        }
        if (sides > 2) {
            // init vars
            var step:Number, qtrStep:Number, start:Number, n:Number, dx:Number, dy:Number;
            // calculate length of sides
            step = (Math.PI * 2) / sides;
            qtrStep = step / 4;
            // calculate starting angle in radians
            start = (angle / 180) * Math.PI;
            target.moveTo(x + (Math.cos(start) * outerRadius), y - (Math.sin(start) * outerRadius));
            // draw lines
            for (n = 1; n <= sides; ++n) {
                dx = x + Math.cos(start + (step * n) - (qtrStep * 3)) * innerRadius;
                dy = y - Math.sin(start + (step * n) - (qtrStep * 3)) * innerRadius;
                target.lineTo(dx, dy);
                dx = x + Math.cos(start + (step * n) - (qtrStep * 2)) * innerRadius;
                dy = y - Math.sin(start + (step * n) - (qtrStep * 2)) * innerRadius;
                target.lineTo(dx, dy);
                dx = x + Math.cos(start + (step * n) - qtrStep) * outerRadius;
                dy = y - Math.sin(start + (step * n) - qtrStep) * outerRadius;
                target.lineTo(dx, dy);
                dx = x + Math.cos(start + (step * n)) * outerRadius;
                dy = y - Math.sin(start + (step * n)) * outerRadius;
                target.lineTo(dx, dy);
            }
            // This is complete overkill... but I had it done already. :)
            if (holeSides > 2) {
                step = (Math.PI * 2) / holeSides;
                target.moveTo(x + (Math.cos(start) * holeRadius), y - (Math.sin(start) * holeRadius));
                for (n = 1; n <= holeSides; ++n) {
                    dx = x + Math.cos(start + (step * n)) * holeRadius;
                    dy = y - Math.sin(start + (step * n)) * holeRadius;
                    target.lineTo(dx, dy);
                }
            }
        }
    }

    /**
     * draws a line between two points. Make it horizontal or vertical
     *
     * @param target Graphics on which the gear is to be drawn.
     * @param x x coordinate of the center of the gear
     * @param y y coordinate of the center of the gear
     * @param sides number of teeth on gear. (must be > 2)
     *
     *
     */
    public static function drawLine(target:Graphics, x:Number, y:Number, length:Number, direction:String = DrawingShapes.HORIZONTAL_LINE):void {
        target.moveTo(x, y);
        switch (direction) {
            case DrawingShapes.HORIZONTAL_LINE :
                target.lineTo(x + length, y);
                break;
            case DrawingShapes.VERTICAL_LINE :
                target.lineTo(x, y + length);
                break;
        }
    }

    /*
     * new abs function, about 25x faster than Math.abs
     */
    private static function abs(value:Number):Number {
        return value < 0 ? -value : value;
    }

    /*
     * new ceil function about 75% faster than Math.ceil.
     */
    private static function ceil(value:Number):Number {
        return (value % 1) ? int(value) + 1 : value;
    }
}
}